﻿using System.Diagnostics;
using Beaker.OpenCube.Render.Maths;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics.CodeAnalysis;

namespace Beaker.OpenCube.Render.Specs
{
	[ExcludeFromCodeCoverage]
	[TestClass]
	public class VectorSpecs
	{
		#region Negate

		[TestMethod]
		public void OperatorNegate_Vector_Vector()
		{
			// arrange
			Vector3 v = new Vector3( 5, -7, 12 );

			// act
			Vector3 nv = -v;

			// assert
			nv.X.Should().Be( -5 );
			nv.Y.Should().Be( 7 );
			nv.Z.Should().Be( -12 );
		}

		[TestMethod]
		public void InstanceNegate_Vector_Vector()
		{
			// arrange
			Vector3 v = new Vector3( 5, -7, 12 );

			// act
			Vector3 nv = v.Negate();

			// assert
			nv.X.Should().Be( -5 );
			nv.Y.Should().Be( 7 );
			nv.Z.Should().Be( -12 );
		}

		[TestMethod]
		public void StaticNegate_Vector_Vector()
		{
			// arrange
			Vector3 v = new Vector3( 5, -7, 12 );

			// act
			Vector3 nv = Vector3.Negate( v );

			// assert
			nv.X.Should().Be( -5 );
			nv.Y.Should().Be( 7 );
			nv.Z.Should().Be( -12 );
		}

		#endregion
		#region Additions

		[TestMethod]
		public void OperatorAdd_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = v1 + v2;

			// assert
			v3.X.Should().Be( 3 + 7 );
			v3.Y.Should().Be( 8 + 23 );
			v3.Z.Should().Be( 13 + 43 );
		}

		[TestMethod]
		public void InstanceAdd_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = v1.Add( v2 );

			// assert
			v3.X.Should().Be( 3 + 7 );
			v3.Y.Should().Be( 8 + 23 );
			v3.Z.Should().Be( 13 + 43 );
		}

		[TestMethod]
		public void StaticAdd_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = Vector3.Add( v1, v2 );

			// assert
			v3.X.Should().Be( 3 + 7 );
			v3.Y.Should().Be( 8 + 23 );
			v3.Z.Should().Be( 13 + 43 );
		}

		#endregion
		#region Subtractions

		[TestMethod]
		public void OperatorSubtract_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = v2 - v1;

			// assert
			v3.X.Should().Be( 7 - 3 );
			v3.Y.Should().Be( 23 - 8 );
			v3.Z.Should().Be( 43 - 13 );
		}

		[TestMethod]
		public void InstanceSubtract_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = v2.Subtract( v1 );

			// assert
			v3.X.Should().Be( 7 - 3 );
			v3.Y.Should().Be( 23 - 8 );
			v3.Z.Should().Be( 43 - 13 );
		}

		[TestMethod]
		public void StaticSubtract_VectorVector_Vector()
		{
			// arrange
			var v1 = new Vector3( 3, 8, 13 );
			var v2 = new Vector3( 7, 23, 43 );

			// act
			Vector3 v3 = Vector3.Subtract( v2, v1 );

			// assert
			v3.X.Should().Be( 7 - 3 );
			v3.Y.Should().Be( 23 - 8 );
			v3.Z.Should().Be( 43 - 13 );
		}

		#endregion
		#region Multiplications

		[TestMethod]
		public void OperatorMultiply_VectorDouble_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 76, 51 );
			double d = 3;

			// act
			Vector3 v2 = v1 * d;

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void InstanceMultiply_VectorDouble_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 76, 51 );
			double d = 3;

			// act
			Vector3 v2 = v1.Multiply( d );

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void StaticMultiply_VectorDouble_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 76, 51 );
			double d = 3;

			// act
			Vector3 v2 = Vector3.Multiply( v1, d );

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void OperatorMultiply_DoubleVector_Vector()
		{
			// arrange
			double d = 3;
			Vector3 v1 = new Vector3( 13, 76, 51 );

			// act
			Vector3 v2 = d * v1;

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void ExtensionMultiply_DoubleVector_Vector()
		{
			// arrange
			double d = 3;
			Vector3 v1 = new Vector3( 13, 76, 51 );

			// act
			Vector3 v2 = d.Multiply( v1 );

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void StaticMultiply_DoubleVector_Vector()
		{
			// arrange
			double d = 3;
			Vector3 v1 = new Vector3( 13, 76, 51 );

			// act
			Vector3 v2 = Vector3.Multiply( d, v1 );

			// assert
			v2.X.Should().Be( 13 * 3 );
			v2.Y.Should().Be( 76 * 3 );
			v2.Z.Should().Be( 51 * 3 );
		}

		[TestMethod]
		public void OperatorMultiply_DotProduct_VectorVector_Double()
		{
			// arrange
			Vector3 v1 = new Vector3( 8, 16, 32 );
			Vector3 v2 = new Vector3( 5, 13, 43 );

			// act
			double d = v1 * v2;

			// assert
			d.Should().Be( 8 * 5 + 16 * 13 + 32 * 43 );
		}

		[TestMethod]
		public void InstanceDotProduct_VectorVector_Double()
		{
			// arrange
			Vector3 v1 = new Vector3( 8, 16, 32 );
			Vector3 v2 = new Vector3( 5, 13, 43 );

			// act
			double d = v1.DotProduct( v2 );

			// assert
			d.Should().Be( 8 * 5 + 16 * 13 + 32 * 43 );
		}

		[TestMethod]
		public void StaticDotProduct_VectorVector_Double()
		{
			// arrange
			Vector3 v1 = new Vector3( 8, 16, 32 );
			Vector3 v2 = new Vector3( 5, 13, 43 );

			// act
			double d = Vector3.DotProduct( v1, v2 );

			// assert
			d.Should().Be( 8 * 5 + 16 * 13 + 32 * 43 );
		}

		[TestMethod]
		public void OperatorXor_CrossProduct_VectorVector_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 7, 15, 31 );
			Vector3 v2 = new Vector3( 7, 53, 19 );

			// act
			Vector3 v3 = v1 ^ v2;

			// assert
			v3.X.Should().Be( v1.Y * v2.Z - v1.Z * v2.Y );
			v3.Y.Should().Be( v1.Z * v2.X - v1.X * v2.Z );
			v3.Z.Should().Be( v1.X * v2.Y - v1.Y * v2.X );
		}

		[TestMethod]
		public void InstanceCrossProduct_VectorVector_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 7, 15, 31 );
			Vector3 v2 = new Vector3( 7, 53, 19 );

			// act
			Vector3 v3 = v1.CrossProduct( v2 );

			// assert
			v3.X.Should().Be( v1.Y * v2.Z - v1.Z * v2.Y );
			v3.Y.Should().Be( v1.Z * v2.X - v1.X * v2.Z );
			v3.Z.Should().Be( v1.X * v2.Y - v1.Y * v2.X );
		}

		[TestMethod]
		public void StaticCrossProduct_VectorVector_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 7, 15, 31 );
			Vector3 v2 = new Vector3( 7, 53, 19 );

			// act
			Vector3 v3 = Vector3.CrossProduct( v1, v2 );

			// assert
			v3.X.Should().Be( v1.Y * v2.Z - v1.Z * v2.Y );
			v3.Y.Should().Be( v1.Z * v2.X - v1.X * v2.Z );
			v3.Z.Should().Be( v1.X * v2.Y - v1.Y * v2.X );
		}

		#endregion
		#region Divisions

		[TestMethod]
		public void OperatorDivision_VectorFloat_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 1, 2, 3 );
			double d = 2;

			// act
			Vector3 v2 = v1 / d;

			// assert
			v2.X.Should().Be( 1d / 2d );
			v2.Y.Should().Be( 2d / 2d );
			v2.Z.Should().Be( 3d / 2d );
		}

		[TestMethod]
		public void InstanceDivision_VectorFloat_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 1, 2, 3 );
			double d = 2;

			// act
			Vector3 v2 = v1.Divide( d );

			// assert
			v2.X.Should().Be( 1d / 2d );
			v2.Y.Should().Be( 2d / 2d );
			v2.Z.Should().Be( 3d / 2d );
		}

		[TestMethod]
		public void StaticDivision_VectorFloat_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 1, 2, 3 );
			double d = 2;

			// act
			Vector3 v2 = Vector3.Divide( v1, d );

			// assert
			v2.X.Should().Be( 1d / 2d );
			v2.Y.Should().Be( 2d / 2d );
			v2.Z.Should().Be( 3d / 2d );
		}

		#endregion
		#region Magnitude

		private const double RequiredPrecision = 0.0000000001d;

		[TestMethod]
		public void InstanceMagnitude()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 17, 23 );

			// act
			double m = v1.Magnitude();

			// assert
			m.Should().BeApproximately( Math.Sqrt( 13 * 13 + 17 * 17 + 23 * 23 ), RequiredPrecision );
		}

		[TestMethod]
		public void StaticMagnitude()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 17, 23 );

			// act
			double m = Vector3.Magnitude( v1 );

			// assert
			m.Should().BeApproximately( Math.Sqrt( 13 * 13 + 17 * 17 + 23 * 23 ), RequiredPrecision );
		}

		[TestMethod]
		public void InstanceMagnitudePower()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 17, 23 );

			// act
			double m = v1.MagnitudePower();

			// assert
			m.Should().Be( 13 * 13 + 17 * 17 + 23 * 23 );
		}

		[TestMethod]
		public void StaticMagnitudePower()
		{
			// arrange
			Vector3 v1 = new Vector3( 13, 17, 23 );

			// act
			double m = Vector3.MagnitudePower( v1 );

			// assert
			m.Should().Be( 13 * 13 + 17 * 17 + 23 * 23 );
		}


		#endregion
		#region Normal

		[TestMethod]
		public void InstanceNormal_Vector_Vector_Null()
		{
			// arrange
			Vector3 v1 = new Vector3( 0, 0, 0 );

			// act
			Vector3 n = v1.Normalize();

			// assert
			Double.IsNaN( n.X ).Should().BeTrue();
			Double.IsNaN( n.Y ).Should().BeTrue();
			Double.IsNaN( n.Z ).Should().BeTrue();
		}

		[TestMethod]
		public void InstanceNormal_Vector_Vector()
		{
			// arrange
			Vector3 v1 = new Vector3( 10, 15, 20 );

			// act
			Vector3 n = v1.Normalize();

			// assert
			var m = 1d / Math.Sqrt( 10 * 10 + 15 * 15 + 20 * 20 );
			n.X.Should().Be( 10 * m );
			n.Y.Should().Be( 15 * m );
			n.Z.Should().Be( 20 * m );
		}

		#endregion
	}
}
